/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../regs.h"

#include "../smc_tbls.h"

#include "ctx.h"
#include "private.h"

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
#define L(fmt,...) printk(KERN_INFO fmt "\n", ##__VA_ARGS__)
void smc_mem_clk_dump(struct smc_mem_clk *tbl)
{
	u32 tmp;

	L("SMC_MEM_CLK START");

	tmp = get_unaligned_be32(&tbl->mem_pll_func_ctl_0);
	L("mem_pll_func_ctl_0=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->mem_pll_func_ctl_1);
	L("mem_pll_func_ctl_1=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->mem_pll_func_ctl_2);
	L("mem_pll_func_ctl_2=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->mem_pll_ad_func_ctl);
	L("mem_pll_ad_func_ctl=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->mem_pll_dq_func_ctl);
	L("mem_pll_dq_func_ctl=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->mem_clk_pm_ctl);
	L("mem_clk_pm_ctl=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->dll_ctl);
	L("dll_ctl=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->mem_pll_ss_0);
	L("mem_pll_ss_0=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->mem_pll_ss_1);
	L("mem_pll_ss_1=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->clk);
	L("clk=0x%08x",tmp);

	L("cg_eng_pll_func_ctl_0=0x%08x",tmp);

	L("SMC_MEM_CLK END");
}
#endif

#define STUTER_DISPLAYS_N_MAX 2
void smc_mem_clk_displays_adjust(struct ctx *ctx, struct smc_lvl *smc_lvl,
								u8 displays_n)
{
	u32 dpg_pipe_stutter_ctl;
	u32 mem_clk;

	dpg_pipe_stutter_ctl = rr32(ctx->dev, DPG_PIPE_STUTTER_CTL);
	mem_clk = get_unaligned_be32(&smc_lvl->mem_clk.clk);

	if (mem_clk <= MEM_CLK_STUTTER_MODE_THRESHOLD
				&& (dpg_pipe_stutter_ctl & DPSC_STUTTER_ENA)
				&& displays_n <= STUTER_DISPLAYS_N_MAX) {
		smc_lvl->mc_flgs |= SMC_MC_FLGS_STUTTER_ENA;
		/* XXX: "gmc" power gating is disabled */
	}

	//TODO:far from being finished
}
#undef STUTER_DISPLAYS_N_MAX

long smc_mem_clk_from_atb_pp(struct ctx *ctx, struct atb_pp_lvl *atb_lvl,
					struct smc_lvl *smc_lvl, u8 dll_state)
{
	struct smc_mem_clk *smc_mem_clk;
	struct dev_drv_data *dd;
	u8 strobe_mode;
	struct atb_mem_pll atb_mem_pll;
	long r;
	u32 mem_pll_func_ctl_0;
	u32 mem_pll_func_ctl_1;
	u32 mem_pll_ad_func_ctl;
	u32 mem_pll_ss_0;
	u32 mem_pll_ss_1;
	u32 mem_clk_pm_ctl;
	u32 mem_pll_dq_func_ctl;

	if (smc_lvl->strobe_mode & SMC_STROBE_MODE_ENA)
		strobe_mode = 1;
	else
		strobe_mode = 0;

	dd = pci_get_drvdata(ctx->dev);

	r = atb_mem_pll_compute(dd->atb, atb_lvl->mem_clk, strobe_mode,
								&atb_mem_pll);
	if (r == -ATB_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to compute the parameters of the memory pll\n");
		return -SI_ERR;
	}

	mem_pll_func_ctl_0 = dd->pp.clks_regs.mem_pll_func_ctl_0;
	
	mem_pll_func_ctl_0 &= ~MPFC0_BW_CTL;
	mem_pll_func_ctl_0 |= set(MPFC0_BW_CTL, atb_mem_pll.bw_ctl);

	mem_pll_func_ctl_1 = dd->pp.clks_regs.mem_pll_func_ctl_1;
	mem_pll_func_ctl_1 &= ~(MPFC1_CLK_INTEGER | MPFC1_CLK_FRAC
							| MPFC1_VCO_MODE);
	mem_pll_func_ctl_1 |= set(MPFC1_CLK_INTEGER, atb_mem_pll.fb_integer) 
				| set(MPFC1_CLK_FRAC, atb_mem_pll.fb_frac)
				| set(MPFC1_VCO_MODE, atb_mem_pll.vco_mode);

	mem_pll_ad_func_ctl = dd->pp.clks_regs.mem_pll_ad_func_ctl;
	mem_pll_ad_func_ctl &= ~MPAFC_Y_CLK_POST_DIV;
	mem_pll_ad_func_ctl |= set(MPAFC_Y_CLK_POST_DIV, atb_mem_pll.post_div);

	mem_pll_dq_func_ctl = dd->pp.clks_regs.mem_pll_dq_func_ctl;
	if (ctx->misc_caps & MISC_CAPS_VRAM_IS_GDDR5) {
		mem_pll_dq_func_ctl &= ~(MPDFC_Y_CLK_SEL
							| MPDFC_Y_CLK_POST_DIV);
		if (atb_mem_pll.flgs & ATB_MEM_PLL_Y_CLK_SEL)
			mem_pll_dq_func_ctl |= MPDFC_Y_CLK_SEL;
		mem_pll_dq_func_ctl |=  set(MPDFC_Y_CLK_POST_DIV,
							atb_mem_pll.post_div);
	}

	mem_pll_ss_0 = dd->pp.clks_regs.mem_pll_ss_0;
	mem_pll_ss_1 = dd->pp.clks_regs.mem_pll_ss_1;

	if (ctx->misc_caps & MISC_CAPS_MEM_CLK_SS_ENA) {
		struct atb_ss atb_ss;
		u32 freq_nominal;
		u32 tmp;

		if (ctx->misc_caps & MISC_CAPS_VRAM_IS_GDDR5)
			freq_nominal = atb_lvl->mem_clk << 2;
		else
			freq_nominal = atb_lvl->mem_clk << 1;

		tmp = freq_nominal / ctx->mem_ref_clk;
		tmp = tmp * tmp;

		/* carefull, it uses nominal frequence */
		r = atb_mem_clk_ss_get(dd->atb, freq_nominal, &atb_ss);
		if (r == -ATB_ERR) {
			dev_warn(&ctx->dev->dev, "dyn_pm:unable to find spread spectrum parameters for the nominal memory clock of %u kHz\n",
						freq_nominal * 10);
		} else {
			u32 clk_s;
			u32 clk_v;

			clk_s = ctx->mem_ref_clk * 5 / atb_ss.rate;
			clk_v = (((131 * atb_ss.percentage * atb_ss.rate)
						/ 100) * tmp) / freq_nominal;

			mem_pll_ss_0 &= ~MPS0_CLK_V;
			mem_pll_ss_0 |= set(MPS0_CLK_V, clk_v);

			mem_pll_ss_1 &= ~MPS1_CLK_S;
			mem_pll_ss_1 |= set(MPS1_CLK_S, clk_s);
		}
	}

	mem_clk_pm_ctl = dd->pp.clks_regs.mem_clk_pm_ctl;
	mem_clk_pm_ctl &= ~MCPC_DLL_SPEED;
	mem_clk_pm_ctl |= set(MCPC_DLL_SPEED, atb_mem_pll.dll_speed);

	if (dll_state)
		mem_clk_pm_ctl |= MCPC_MRDCK0_PDNB | MCPC_MRDCK1_PDNB;
	else
		mem_clk_pm_ctl &= ~(MCPC_MRDCK0_PDNB | MCPC_MRDCK1_PDNB);

	smc_mem_clk = &smc_lvl->mem_clk;
	put_unaligned_be32(atb_lvl->mem_clk, &smc_mem_clk->clk);
	put_unaligned_be32(mem_pll_func_ctl_0,
					&smc_mem_clk->mem_pll_func_ctl_0);
	put_unaligned_be32(mem_pll_func_ctl_1,
					&smc_mem_clk->mem_pll_func_ctl_1);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_func_ctl_2,
					&smc_mem_clk->mem_pll_func_ctl_2);
	put_unaligned_be32(mem_pll_ad_func_ctl,
					&smc_mem_clk->mem_pll_ad_func_ctl);
	put_unaligned_be32(mem_pll_dq_func_ctl,
					&smc_mem_clk->mem_pll_dq_func_ctl);
	put_unaligned_be32(mem_clk_pm_ctl, &smc_mem_clk->mem_clk_pm_ctl);
	put_unaligned_be32(dd->pp.clks_regs.dll_ctl, &smc_mem_clk->dll_ctl);
	put_unaligned_be32(mem_pll_ss_0, &smc_mem_clk->mem_pll_ss_0);
	put_unaligned_be32(mem_pll_ss_1, &smc_mem_clk->mem_pll_ss_1);
	return 0;
}
